In [1]:
from IPython.display import Image
Image(filename='./dz.jpg')
Out[1]:
make
es una herramienta pensada para automatizar la compilacion de programas escritos en C
/C++
. Compilar no siempre es un proceso que se pueda realizar a mano, por ello surgen estas herramientas. Algunas herramientas similares para otros lenguajes son: Ant
, Cmake
,Gradle
o Grunt
.
make
no hace magia, sino que realiza el proceso mediante las instrucciones definidas en el archivo Makefile
. En el mismo, definimos que y como hacer cada parte del proceso.
Los archivos Makefile
definen las instrucciones a ejecutar en el proceso de compilacion (mas adelante veremos que es mas que eso). Cada instruccion es de la forma:
target: dependencias
comando/s
Target
: Son los distintos pasos/acciones que podemos realizar. Cada nombre es unico.Dependencias
: Son los requisitos que se deben cumplir cada target antes de poder ser ejecutado. Pueden ser tanto targets
como archivos.Comandos
: Son los pasos a ejecutar en el target definido
In [1]:
cp ./makefile_ej1 makefile
cat makefile
In [2]:
make compile
Si se ejecuta make
sin ningun target especificado, se ejecutara el primer target que se encuentre en el archivo. En nuestro caso el unico es compile
entonces:
In [3]:
make
Al ejecutar make
busca en el directorio un archivo con el nombre makefile
o Makefile
para ejecutar. Si nuestro archivo Makefile
tiene un nombre diferente (vaya uno a saber por que), se puede indicar el nombre del mismo con el parametro -f
In [4]:
make -f ./makefile_ej1 compile
Las dependencias pueden ser de dos tipos:
targets
: Los targets
deben ejecutarse antes de que se ejecute este target
archivos
: Los archivos
deben existir el archivo para que se ejecute el target
. Ademas, desde la ultima vez que se ejecuto el target el archivo se debe haber modificado.
In [5]:
cp ./makefile_ej2 makefile
cat makefile
In [6]:
make
Si no queremos ver que comandos se ejecutan, se puede usar el parametro -s
In [7]:
make -s
In [8]:
make bar -s
Como bar
depende de foo
, al ejecutar bar
este target primero intenta ejecutar foo
In [9]:
rm -f *.txt
touch text_support.txt
cp ./makefile_ej3 makefile
cat makefile
In [10]:
make doSomething
In [11]:
make doSomething
Si bien doSomething
depende de text.txt, text.txt depende del archivo _textsupport.txt . Como en el segundo llamado este archivo no se modifica, el target de construccion text.txt
no se ejecuta.
In [12]:
echo "Bye bye world" > text_support.txt
In [13]:
make doSomething
Si modifico text_support.txt
el target vuelve a ejecutarse nuevamente. Esto es util cuando compilamos TDAs dentro de nuestros programas, y no volver a compilar el TDA si los fuentes no fueron modificados
El target clean se suele utilizar para limpiar los archivos intermedios que se generan durante la compilacion:
clean:
rm -f <to_clean1> <to_clean2> <to_clean3> ... <to_clean n>
Es a veces una buena idea tener 2 targets
distintos para limpiar archivos intermedios y otro para limpiar todo lo que no sea un archivo de codigo fuente.
In [14]:
cp ./makefile_ej4 makefile
cat makefile
In [15]:
make
In [16]:
make run
In [17]:
./e_example
In [18]:
make clean
In [19]:
./e_example
In [2]:
from IPython.display import Image
Image(filename='./su.png')
Out[2]:
Una parte importante de la vida es recordar que cualquier cosa ejecutable es codigo, y como tal, puede cambiar muy rapido. Entonces, es una muy buena idea que se pueda cambiar facilmente. Los makefiles permiten definir variables, y en general se suelen usar las siguientes (los nombres son por convencion):
Para utilizar el contenido de la variable se utiliza $(<variable>)
In [20]:
cp ./makefile_ej5 makefile
cat makefile
In [21]:
make run
Puedo tambien cambiarle el valor a las variables o definirlas al momento de llamar a make
In [22]:
make run DEFAULT=english_example
Las variables se pueden utilizar en cualquier parte del codigo, y obtener resultados mas complejos como por ejemplo:
$(LIB).o: $(LIB).c $(LIB).h
$(CC) $(C_FLAGS) $(LIB).c -c
Esto nos permitiria elegir que biblioteca compilar. Este y otros trucos con variables ya son mas avanzados y tienen usos mas especificos, asi que por ahora son solo una mencion.
Los Makefile
soportan expresiones regulares para poder generalizar los targets. En lo personal, si bien simplifican el trabajo a la hora de escribir un Makefile
, usar este tipo de cosas no es una muy buena practica ya que usualmente se termina usando mal. Los problemas asociados son la falta de legibilidad y la perdida de control sobre el proceso completo de compilacion. Queda a cargo del lector interiorizarse mas sobre esto si es que le interesa.
Como ya se mostro, los targets pueden ejecutar casi cualquier linea de bash. Es entonces una buena idea crear un target que nos permita correr Valgrind sin tener que estar recordando todos esos largos comandos. Un snipet util para ello seria:
VALGRIND = valgrind
V_FLAGS = --track-origins=yes --leak-check=full --trace-children
VALGRIND_V = $(VALGRIND) $(V_FLAGS) -v
VALGRIND_Q = $(VALGRIND) $(V_FLAGS) -q
valgrindRun: $(EXEC)
$(VALGRIND_V) ./$(EXEC)
Dentro de los archivos Makefile se pueden hacer comentarios inline utilizando '#'. Son muy utiles a la hora de por ejemplo determinar el objetivo de una variable
PADRON=XXXXX
ARCHIVOS=*.h *.c #Tipos de archivos que se van a agregar
CUATRIMESTRE=2016-2C
ENTREGA=Pila
ENCODING=ISO-8859-1
OUTPUTFILE=out.ps #archivo intermedio
FORMATO=landscape #portrait o landscape (vertical u horizontal)
COLUMNAS=2 #paginas por hoja (en columnas)
NUMEROS_LINEA=1 #cada cuantas lineas se imprime el numero de linea
ARCHIVO_ENTREGA=$(PADRON)-$(ENTREGA)
NOMBRE_ZIP= $(ARCHIVO_ENTREGA).zip
NOMBRE_PDF= $(ARCHIVO_ENTREGA).pdf
ENCABEZADO = "[75.41] Algoritmos y Programacion II"
PIE = "Padrón $(PADRON) (curso $(CUATRIMESTRE)) Entrega: $(ENTREGA)"
pdf: clean
a2ps $(ARCHIVOS) -Av --header=$(ENCABEZADO) --footer=$(PIE) --line-numbers=$(NUMEROS_LINEA) --borders=yes --columns=$(COLUMNAS) --$(FORMATO) --output=$(OUTPUTFILE) --encoding=$(ENCODING) --tabsize=4 --major=columns --toc | ps2pdf $(OUTPUTFILE) $(NOMBRE_PDF)
rm *.ps #elimino los archivos temporales
entrega: pdf
zip $(NOMBRE_ZIP) $(ARCHIVOS) *.pdf
clean_entrega: clean
rm *.zip *.pdf
Este snippet permite generar un zip con el codigo para entregar y ademas genera un pdf con el codigo listo para imprimir (Si, monoespaciado y con numeros de linea).
In [1]:
cp ./makefile_ex makefile
cat makefile
In [2]:
make entrega
Para poder utilizarlo es necesario tener los paquetes: a2ps
, ps2pdf
y zip
. Siempre se pueden instalar haciendo:
sudo apt-get update && sudo apt-get install a2ps ps2pdf zip
DISCLAIMER: Los targets dependen de las instrucciones de clean ya definidas. Si alguien define clean como rm *
no nos responsabilizamos
Los build scripts como los Makefile ahorran tiempo. Permiten que cualquiera pueda compilar un codigo de manera sencilla y sin tener que estar escribiendo N comandos en orden. Incluso, este tipo de archivos permiten que nuestro codigo se compile y pruebe automaticamente mientras usamos control de versiones:
In [1]:
from IPython.display import Image
Image(filename='./end.jpg')
Out[1]:
Para mas informacion, siga en contacto
The End